package com.winbaoxian.testng.core.action;

import com.winbaoxian.testng.constant.WinTestNGConstant;
import com.winbaoxian.testng.core.common.ParamsExecutor;
import com.winbaoxian.testng.exception.WinTestNgException;
import com.winbaoxian.testng.model.core.ActionConfigDTO;
import com.winbaoxian.testng.model.core.TestCasesContext;
import com.winbaoxian.testng.model.core.action.TplActionSettings;
import com.winbaoxian.testng.model.core.log.TestReportDataTestCaseDTO;
import com.winbaoxian.testng.model.dto.ActionTemplateDTO;
import com.winbaoxian.testng.service.WinTestNGService;
import com.winbaoxian.testng.utils.ConsoleLogUtils;
import com.winbaoxian.testng.utils.CopyUtils;
import com.winbaoxian.common.freemarker.utils.JsonUtils;
import com.winbaoxian.testng.utils.WinTestNGLogUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author dongxuanliang252
 * @date 2019-03-06 17:46
 */
@Component
@Slf4j
public class TplAction implements IAction<TplActionSettings> {

    @Resource
    private ActionExecutor actionExecutor;
    @Resource
    private ParamsExecutor paramsExecutor;
    @Resource
    private WinTestNGService winTestNGService;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    private static long SLEEP_TIME = 1000 * 5L;
    private static long MAX_WAIT_TIME = 1000 * 60 * 5L;

    @Override
    public Object execute(TplActionSettings settings, ActionConfigDTO actionConfig, TestCasesContext context) throws Exception {
        if (settings == null || settings.getTemplateId() == null) {
            return null;
        }
        ActionTemplateDTO actionTemplateDTO = winTestNGService.getActionTemplate(settings.getTemplateId());
        if (actionTemplateDTO == null) {
            throw new WinTestNgException(String.format("action template(%s) can not be found", settings.getTemplateId()));
        }
        if (BooleanUtils.isNotFalse(actionTemplateDTO.getDeleted())) {
            throw new WinTestNgException(String.format("this action template(%s) is not available", settings.getTemplateId()));
        }
        if (CollectionUtils.isEmpty(actionTemplateDTO.getActions())) {
            throw new WinTestNgException(String.format("no action set in template(%s)", settings.getTemplateId()));
        }
        boolean useCacheFlag = false;
        if (BooleanUtils.isTrue(actionTemplateDTO.getConcurrentCacheSupport()) && MapUtils.isNotEmpty(settings.getMappings())) {
            useCacheFlag = true;
        }
        String[] testReportTitles = WinTestNGLogUtils.INSTANCE.getTestReportTitles(actionConfig, context);
        String tFlowActionLogPrefix = WinTestNGLogUtils.INSTANCE.getActionLogPrefix(actionConfig, context);
        TestReportDataTestCaseDTO reportDataContext = context.getReportDataContext();
        if (useCacheFlag) {
            StringBuilder keySb = new StringBuilder();
            keySb.append(String.format("TPL_%s", settings.getTemplateId()));
            List<String> paramsKeys = new ArrayList<>(settings.getMappings().keySet());
            Collections.sort(paramsKeys);
            for (String paramsKey : paramsKeys) {
                keySb.append(String.format("-%s_%s", paramsKey, settings.getMappings().get(paramsKey)));
            }
            String key = keySb.toString();
            String lockKey = key + ".lock";
            long startTime = System.currentTimeMillis();
            while (true) {
                Boolean isLock = redisTemplate.boundValueOps(lockKey).setIfAbsent(1);
                if (isLock) {
                    redisTemplate.expire(lockKey, WinTestNGConstant.TPL_CACHE_LOCK_TIMEOUT, TimeUnit.SECONDS);
                    try {
                        List<Object> oList = redisTemplate.boundHashOps(key).values();
                        if (CollectionUtils.isNotEmpty(oList) && oList.get(0) != null) {
                            String message = String.format("[%s] TPL(%s): %s, 命中缓存, key: %s, value: %s", tFlowActionLogPrefix, actionTemplateDTO.getId(), actionTemplateDTO.getName(), key, JsonUtils.INSTANCE.toJSONString(oList.get(0)));
                            if (!redisTemplate.boundHashOps(key).hasKey(reportDataContext.getReportUuid())) {
                                redisTemplate.boundHashOps(key).put(reportDataContext.getReportUuid(), oList.get(0));
                                redisTemplate.expire(key, WinTestNGConstant.TPL_CACHE_LOCK_TIMEOUT, TimeUnit.SECONDS);
                                context.getReportDataContext().getTplLockCache().put(key, oList.get(0));
                            }
                            log.info(message);
                            ConsoleLogUtils.INSTANCE.log(reportDataContext.getReportUuid(), message);
                            reportDataContext.logText(message, testReportTitles);
                            return oList.get(0);
                        }
                        Object retObject = executeInternal(settings, actionConfig, actionTemplateDTO, context);
                        redisTemplate.boundHashOps(key).put(reportDataContext.getReportUuid(), retObject);
                        redisTemplate.expire(key, WinTestNGConstant.TPL_CACHE_LOCK_TIMEOUT, TimeUnit.SECONDS);
                        context.getReportDataContext().getTplLockCache().put(key, retObject);
                        String message = String.format("[%s] TPL(%s): %s, 添加缓存, key: %s, value: %s", tFlowActionLogPrefix, actionTemplateDTO.getId(), actionTemplateDTO.getName(), key, JsonUtils.INSTANCE.toJSONString(retObject));
                        log.info(message);
                        ConsoleLogUtils.INSTANCE.log(reportDataContext.getReportUuid(), message);
                        reportDataContext.logText(message, testReportTitles);
                        return retObject;
                    } catch (Exception e) {
                        throw e;
                    } finally {
                        redisTemplate.delete(lockKey);
                    }
                } else if (System.currentTimeMillis() - startTime <= MAX_WAIT_TIME) {
                    Thread.sleep(SLEEP_TIME);
                } else {
                    throw new WinTestNgException(String.format("[%s] TPL(%s): %s, 等待锁失败, key: %s", tFlowActionLogPrefix, actionTemplateDTO.getId(), actionTemplateDTO.getName(), key));
                }
            }
        } else {
            return executeInternal(settings, actionConfig, actionTemplateDTO, context);
        }
    }

    private Object executeInternal(TplActionSettings settings, ActionConfigDTO actionConfig, ActionTemplateDTO actionTemplateDTO, TestCasesContext context) throws Exception {
        context.pushActionConfig(actionConfig);
        try { //shallow copy TestCasesContext
            TestCasesContext newContext = CopyUtils.INSTANCE.shallowClone(context);
            newContext.setGlobalParams(new HashMap<>());
            newContext.addGlobalParams(settings.getMappings());
            actionExecutor.execute(actionTemplateDTO.getActions(), newContext);
            if (StringUtils.isEmpty(actionTemplateDTO.getResult())) {
                return null;
            }
            String result = paramsExecutor.render(actionTemplateDTO.getResult(), newContext);
            return JsonUtils.INSTANCE.parseObject(result);
        } catch (Exception e) {
            throw e;
        } finally {
            context.popActionConfig();
        }
    }

}
